在开发中,若是遇到结构体中的私有变量(小写变量),需要出现在json Marshal结果中,使用json外包显然是无法做到的,因为外包是无法看到私有变量的,reflect机制需要的是大写变量,但就是需要这样的操作,怎么办呢,可以实现该结构体自有Marshal方法。

看到一篇好文章:Custom JSON Marshalling in Go,以下是翻译。

补:

有人问我,这个小写变量没啥软用啊,你咋不大写?

额,我这里自己对结构体编写了构造函数,我有一些初始设置是写在结构体里的,不想被外界修改,所以使用了这个操作,另外我可以借助这个操作来更改实际输出给外部的结构体格式。

当然,一切以项目需求为主,img

Go的encoding/json包使得序列化结构体s到JSON数据变得非常容易。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"encoding/json"
"os"
"time"
)

type MyUser struct {
ID int64 `json:"id"`
Name string `json:"name"`
LastSeen time.Time `json:"lastSeen"`
}

func main() {
_ = json.NewEncoder(os.Stdout).Encode(
&MyUser{1, "Ken", time.Now()},
)
}

Output:

1
{"id":1,"name":"Ken","lastSeen":"2009-11-10T23:00:00Z"}

但是,如果我们想要改变其中一个字段值的显示方式呢?例如,假设我想LastSeen成为一个unix时间戳。

简单的解决方案是引入另一个辅助struct,并使用方法中正确格式化的值填充它MarshalJSON

1
2
3
4
5
6
7
8
9
10
11
func (u *MyUser) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
ID int64 `json:"id"`
Name string `json:"name"`
LastSeen int64 `json:"lastSeen"`
}{
ID: u.ID,
Name: u.Name,
LastSeen: u.LastSeen.Unix(), // 调换了参数
})
}

这是有用的,但是当有很多字段时它会变得很麻烦。如果我们可以将原始内容嵌入struct到辅助中struct并使其继承所有不需要更改的字段,这将是好的解决方式。

1
2
3
4
5
6
7
8
9
func (u *MyUser) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
LastSeen int64 `json:"lastSeen"`
*MyUser
}{
LastSeen: u.LastSeen.Unix(),
MyUser: u,
})
}

这里的问题是辅助结构也将继承原始的MarshalJSON方法,导致它进入无限循环(我:这里我不太清楚作者无限循环的意思)。解决方案是为原始类型添加别名。此别名将具有所有相同的字段,但不包含任何方法。

1
2
3
4
5
6
7
8
9
10
func (u *MyUser) MarshalJSON() ([]byte, error) {
type Alias MyUser
return json.Marshal(&struct {
LastSeen int64 `json:"lastSeen"`
*Alias
}{
LastSeen: u.LastSeen.Unix(),
Alias: (*Alias)(u),
})
}

我:辅助structlastSeen会覆盖原始struct中的同名tag参数。

可以使用相同的技术来实现UnmarshalJSON方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func (u *MyUser) UnmarshalJSON(data []byte) error {
type Alias MyUser
aux := &struct {
LastSeen int64 `json:"lastSeen"`
*Alias
}{
Alias: (*Alias)(u),
}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
u.LastSeen = time.Unix(aux.LastSeen, 0)
return nil
}